昨天我們講了列表的基本用法
今天講講列表非常常見的兩種功能
header置頂與下拉刷新(附贈上拉加載)
Flutter不像TableView有section跟row分組的概念⚠️⚠️⚠️
就是一條列表而已
所以如果要做到像以前那樣有分組
可能就是要從資料動手, 插入標題
在builder去做判斷
然後返回不同的header或cell
但如果要做到hrader置頂
就有點麻煩了
Flutter似乎也沒有提供相應的Widget
上網找了一下
好像也沒有其他的做法
大部分是使用套件
好吧
被同事稱為
的我
就來用用fluttercommunity出品的sticky_headers套件
只要照個黃綠紅三個步驟(出現exit0)就好了
官方有提到兩種姿勢
You can place a
StickyHeader
orStickyHeaderBuilder
inside any scrollable content, such as: ListView, GridView, CustomScrollView, SingleChildScrollView or similar.
有一個header屬性, 把你想要出現的widget餵給他
如下官方範例:
class Example extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (context, index) {
return StickyHeader(
header: Container(
height: 50.0,
color: Colors.blueGrey[700],
padding: EdgeInsets.symmetric(horizontal: 16.0),
alignment: Alignment.centerLeft,
child: Text('Header #$index',
style: const TextStyle(color: Colors.white),
),
),
content: Container(
child: Image.network(imageForIndex(index), fit: BoxFit.cover,
width: double.infinity, height: 200.0),
),
);
});
}
}
builder屬性給它一個StickyHeaderWidgetBuilder的call back
可以在裡面做一些判斷
回傳不同的widget當作header
然而不管哪種用法
都要注意content屬性裡面不能
給ListView
不然會爆炸Vertical viewport was given unbounded height.
所以我們改用Column
如下小弟範例:
return ListView.builder(
itemCount: stairs.length,
itemBuilder: (ctx, section) {
return StickyHeaderBuilder(
builder: (ctx, amount) {
return Container(
color: Colors.white,
child: ListTile(
title: Text("Header $section"),
),
);
},
content: Column(
children: List.generate(stairs[section].length, (row) {
return Container(
color: rainbowColors[row],
child: ListTile(
title: Text("Cell $row"),
)
);
})
)
);
}
);
用個費氏數列來當作DEMO
寫完了之後才發現原來官方首頁也有這個例子XD
另外我偶然發現
ListView.builder竟然可以不給itemCount
然後就怎麼滑也滑不完了XDDD
這個部分Flutter有提供RefreshIndicator
用它把你的listView包起來就成了
setState寫在onRefresh去刷新畫面
至於有沒有庫比蒂諾(怎麼聽起來好像瑪利歐裡面會出現的角色)版本的刷新元件呢
答案是肯定的
大概三天後會提到
敬請期待(笑)
就必須自己實作了
有看到參考連結的兩種做法
目前採取了第一種
「利用scrollController去監聽是不是滑到底」
但是這種作法會有如果一開始資料就沒有超過頁面時就無法觸發
然後就怎麼轉也轉不完了XDDD
如果採用第二種
「先在資料裡安插最末筆資料讓itemBuilder去判斷是否到底」
應該就不會有這種問題了
完整的code請見:
class LessonPageListViewRefresh extends StatefulWidget {
@override
_LessonPageListViewRefreshState createState() => _LessonPageListViewRefreshState();
}
class _LessonPageListViewRefreshState extends State<LessonPageListViewRefresh> {
final pageSize = 20;
final maxPage = 4;
int currentPage = 1;
List<int> fibonacci = [];
ScrollController _scrollController = ScrollController();
List<int> _createFib() {
List<int> fib = [];
for (var index = 0;
index < pageSize;
index ++) {
if (index == 0 || index == 1) {
if (fibonacci.isEmpty) {
fib.add(index);
} else if (index == 0) {
fib.add(fibonacci[fibonacci.length - 2] + fibonacci.last);
} else {
fib.add(fibonacci.last + fib.first);
}
} else {
fib.add(fib[index - 2] + fib[index - 1]);
}
}
return fib;
}
@override
void initState() {
super.initState();
fibonacci.addAll(_createFib());
_scrollController.addListener(() {的
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
setState(() {
if (currentPage < maxPage) {
currentPage ++;
fibonacci.addAll(_createFib());
}
});
}
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
setState(() {
currentPage = 1;
fibonacci.clear();
fibonacci.addAll(_createFib());
});
},
child: ListView.builder(
controller: _scrollController,
itemCount: fibonacci.length,
itemBuilder: (ctx, idx) {
//如果設為-1才看得到loading, 但會暫時看不到當頁最後一筆 (條件二是防止看不到所有資料的最後一筆)
if (idx == fibonacci.length - 1 && currentPage < maxPage) {
return Padding(
padding: EdgeInsets.all(16),
child: Center(
child: CircularProgressIndicator()
)
);
} else {
return ListTile(title: Text("${idx + 1} : ${fibonacci[idx]}"));
}
}
)
);
}
}
下集預告:還是列表(輸入與開合)